<?php
/**
 * 易优CMS
 * ============================================================================
 * 版权所有 2016-2028 海南赞赞网络科技有限公司，并保留所有权利。
 * 网站地址: http://www.eyoucms.com
 * ----------------------------------------------------------------------------
 * 如果商业用途务必到官方购买正版授权, 以免引起不必要的法律纠纷.
 * ============================================================================
 * Author: 陈风任 <491085389@qq.com>
 * Date: 2020-05-07
 */

namespace app\api\model\v1;

use think\Db;
use think\Cache;
use think\Config;

/**
 * 微信小程序商城订单支付模型
 */
load_trait('controller/Jump');

class ShopOrderPay extends Base
{
    use \traits\controller\Jump;

    private $miniproInfo = [];

    //初始化
    protected function initialize()
    {
        // 需要调用`Model`的`initialize`方法
        parent::initialize();
        $dataConf = tpSetting("OpenMinicode.conf_".self::$provider);
        $this->miniproInfo = json_decode($dataConf, true);
    }

    // 扣除(deduct) or 恢复(restore) 用于订单支付的积分和余额
    public function handleOrderBalanceScorePay($orderData = [], $action = 'deduct')
    {
        // 操作条件
        $where = [
            'users_id' => $orderData['users_id']
        ];
        // 查询会员当前的余额和积分
        $usersData = Db::name('users')->field('users_id, users_money, scores')->where($where)->find();

        // 添加记录
        $times = getTime();
        $update = [
            'update_time' => $times
        ];
        // 处理支付的余额
        if (!empty($orderData['pay_use_balance']) && floatval($orderData['pay_use_balance']) > 0) {
            // 扣除余额
            if ('deduct' === strval($action)) {
                $update['users_money'] = Db::raw('users_money-' . ($orderData['pay_use_balance']));
            }
            // 恢复余额
            else if ('restore' === strval($action)) {
                $update['users_money'] = Db::raw('users_money+' . ($orderData['pay_use_balance']));
            }
        }
        // 处理支付的积分
        if (!empty($orderData['pay_use_scores']) && !empty($orderData['pay_use_scores_num'])) {
            // 扣除积分
            if ('deduct' === strval($action)) {
                $update['scores'] = Db::raw('scores-' . ($orderData['pay_use_scores_num']));
            }
            // 恢复积分
            else if ('restore' === strval($action)) {
                $update['scores'] = Db::raw('scores+' . ($orderData['pay_use_scores_num']));
            }
        }
        // 更新会员余额、积分数据
        $updateID = Db::name('users')->where($where)->update($update);
        if (!empty($updateID)) {
            // 添加会员余额记录
            if (!empty($orderData['pay_use_balance']) && floatval($orderData['pay_use_balance']) > 0) {
                // 扣除余额
                if ('deduct' === strval($action)) {
                    addUsersMoneyLog($orderData['order_code'], $usersData, $orderData['pay_use_balance'], '订单支付', 3);
                }
                // 恢复余额
                else if ('restore' === strval($action)) {
                    addUsersMoneyLog($orderData['order_code'], $usersData, $orderData['pay_use_balance'], '订单退回', 2);
                }
            }
            // 添加会员积分记录
            if (!empty($orderData['pay_use_scores']) && !empty($orderData['pay_use_scores_num'])) {
                // 扣除积分
                if ('deduct' === strval($action)) {
                    $insert = [
                        'type' => 10,
                        'users_id' => $orderData['users_id'],
                        'score' => $orderData['pay_use_scores_num'],
                        'info' => '订单支付扣除对应积分',
                        'remark' => '订单支付扣除对应积分',
                    ];
                    eyou_give_score($insert, 1, false);
                }
                // 恢复积分
                else if ('restore' === strval($action)) {
                    $insert = [
                        'type' => 11,
                        'users_id' => $orderData['users_id'],
                        'score' => $orderData['pay_use_scores_num'],
                        'info' => '订单退回对应积分',
                        'remark' => '订单退回对应积分',
                    ];
                    eyou_give_score($insert, 2, false);
                }
            }
        }
    }

    // 订单余额支付处理
    public function orderBalancePayHandle($orderData = [])
    {
        $payDetails = [
            'unified_id'     => $orderData['order_id'],
            'unified_number' => $orderData['order_code'],
            'payment_amount' => $orderData['pay_use_cash'],
            'payment_type'   => '余额支付'
        ];
        $update = [
            'order_status'    => 1,
            'pay_name'        => 'balance',
            'wechat_pay_type' => '',
            'pay_details'     => serialize($payDetails),
            'pay_time'        => getTime(),
            'update_time'     => getTime()
        ];
        $result = Db::name('shop_order')->where('order_id', $orderData['order_id'])->update($update);
        if (!empty($result)) {

            // 商品库存、销量处理（付款减库存）
            $goods_stock_deduct = (int)getUsersConfigData('goods.goods_stock_deduct');
            if (!empty($goods_stock_deduct)) {
                $upSpecValue = [];
                $goodsList = Db::name('shop_order_details')->alias('a')
                    ->field('a.product_id, a.num, b.value_id')
                    ->join('__PRODUCT_SPEC_VALUE__ b', 'a.spec_value_id = b.spec_value_id and a.product_id = b.aid', 'LEFT')
                    ->where(['order_id'=>$orderData['order_id']])
                    ->select();
                foreach ($goodsList as $key => $value) {
                    $upSpecValue[] = [
                        'aid' => $value['product_id'],
                        'value_id' => intval($value['value_id']),
                        'quantity' => $value['num'],
                    ];
                }
                model('v1.ShopGoods')->goodsStockDeduct($upSpecValue);
            }

            // 添加订单操作记录
            AddOrderAction($orderData['order_id'], $orderData['users_id'], 0, 1, 0, 1, '支付成功！', '使用积分余额完成支付！');

            // 统计销售额
            eyou_statistics_data(2);
            eyou_statistics_data(3, $orderData['pay_use_cash']);

            // 发送站内信给后台
            $pay_method_arr = config('global.pay_method_arr');
            $orderData['pay_method'] = $pay_method_arr['balance'];
            SendNotifyMessage($orderData, 5, 1, 0);

            // 订单支付通知
            $params = [
                'users_id' => $orderData['users_id'],
                'result_id' => $orderData['order_id'],
            ];
            eyou_send_notice(5, $params, ['buyer', 'seller']);

            // 已开启的微信订阅消息模板
            $where = [
                'send_scene' => ['IN', [5, 6]],
                'template_code' => ['gt', 0],
                'is_open' => 1,
            ];
            $tmplData = Db::name('weixin_template')->where($where)->order('send_scene asc')->getAllWithIndex('template_id');

            $this->success('支付完成', '/pages/order/index', ['orderData' => $orderData, 'tmplData' => $tmplData]);
        }
    }

    // 微信小程序支付
    public function getWechatAppletsPay($openID = '', $outTradeNo = 0, $totalFee = 0)
    {
        // 当前时间戳
        $times = time();

        // 当前时间戳 + openID 经 MD5加密
        $nonceStr = md5($times . $openID);

        // 调用支付接口参数
        $params = [
            'appid'            => $this->miniproInfo['appid'],
            'attach'           => "微信小程序支付",
            'body'             => "商品支付",
            'mch_id'           => $this->miniproInfo['mchid'],
            'nonce_str'        => $nonceStr,
            'notify_url'       => url('api/v1/Api/wxpay_notify', [], true, true, 1, 2),
            'openid'           => $openID,
            'out_trade_no'     => $outTradeNo,
            'spbill_create_ip' => $this->getClientIP(),
            'total_fee'        => strval($totalFee * 100),
            'trade_type'       => 'JSAPI'
        ];

        // 生成参数签名
        $params['sign'] = $this->paramsSign($params);

        // 生成参数XML格式
        $paramsXml = $this->paramsXml($params);

        // 调用接口返回数据
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        $result = $this->httpsPost($url, $paramsXml);

        // 解析XML格式
        $resultData = $this->resultXml($result);

        // 数据返回
        if ($resultData['return_code'] == 'SUCCESS' && $resultData['return_msg'] == 'OK') {
            // 返回支付所需参数
            $returnData = [
                'prepay_id' => $resultData['prepay_id'],
                'nonceStr'  => $nonceStr,
                'timeStamp' => strval($times)
            ];
            // 生成支付所需的签名
            $returnData['paySign'] = $this->paySign($params['nonce_str'], $resultData['prepay_id'], $times);
            return $returnData;
        } else if ($resultData['return_code'] == 'FAIL') {
            if ($resultData['return_msg'] == '签名错误') {
                $resultData['return_msg'] = '小程序支付配置不正确！';
            }
            return $resultData;
        } else {
            return $resultData;
        }
    }

    // 查询订单在微信官方是否已真实完成支付
    public function getWechatPayResult($openID = '', $outTradeNo = 0)
    {
        // 当前时间戳
        $times = getTime();

        // 当前时间戳 + openID 经 MD5加密
        $nonceStr = md5($times . $openID);

        // 调用支付接口参数
        $params = [
            'appid' => $this->miniproInfo['appid'],
            'mch_id' => $this->miniproInfo['mchid'],
            'nonce_str' => $nonceStr,
            'out_trade_no' => $outTradeNo
        ];

        // 生成参数签名
        $params['sign'] = $this->paramsSign($params);

        // 生成参数XML格式
        $paramsXml = $this->paramsXml($params);

        // 调用接口返回数据
        $url = 'https://api.mch.weixin.qq.com/pay/orderquery';
        $result = $this->httpsPost($url, $paramsXml);

        // 解析XML格式
        $resultData = $this->resultXml($result);

        // 返回结果
        return $resultData;
    }

    private function getClientIP()
    {
        if (getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
            $ip = getenv('HTTP_CLIENT_IP');
        } elseif (getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
            $ip = getenv('HTTP_X_FORWARDED_FOR');
        } elseif (getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
            $ip = getenv('REMOTE_ADDR');
        } elseif (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
            $ip = $_SERVER['REMOTE_ADDR'];
        }
        return preg_match('/[\d\.]{7,15}/', $ip, $matches) ? $matches [0] : '';
    }

    private function paramsSign($values = [])
    {
        //签名步骤一：按字典序排序参数
        ksort($values);
        $string = $this->paramsUrl($values);
        //签名步骤二：在string后加入KEY
        $string = $string . '&key=' . $this->miniproInfo['apikey'];
        //签名步骤三：MD5加密
        $string = md5($string);
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    private function paramsUrl($values = [])
    {
        $url = '';
        foreach ($values as $k => $v) {
            if ($k != 'sign' && $v != '' && !is_array($v)) {
                $url .= $k . '=' . $v . '&';
            }
        }
        return trim($url, '&');
    }

    private function paramsXml($values = [])
    {
        if (!is_array($values) || count($values) <= 0) {
            return false;
        }

        $xml = "<xml>";
        foreach ($values as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    private function httpsPost($url = '', $postData = [])
    {
        // 调用系统内置方法
        $result = httpRequest($url, 'POST', $postData);
        return $result;
    }

    private function resultXml($xml = '')
    {
        // 禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }

    private function paySign($nonceStr = '', $prepay_id = '', $timeStamp = 0)
    {
        $data = [
            'appId'     => $this->miniproInfo['appid'],
            'nonceStr'  => $nonceStr,
            'package'   => 'prepay_id=' . $prepay_id,
            'signType'  => 'MD5',
            'timeStamp' => $timeStamp,
        ];
        // 签名步骤一：按字典序排序参数
        ksort($data);
        $string = $this->paramsUrl($data);
        // 签名步骤二：在string后加入KEY
        $string = $string . '&key=' . $this->miniproInfo['apikey'];
        // 签名步骤三：MD5加密
        $string = md5($string);
        // 签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

}